Spring AI快速入门实战

很多人第一次接触大模型开发,脑子里都会冒出一个问题:在Java项目里怎么跟AI对话?是不是要自己拼HTTP请求、解析JSON、处理各种异常?

其实完全不用这么麻烦。Spring官方团队专门搞了一套叫Spring AI的框架,目的就是让Java开发者能像调用普通Service一样调用大模型。今天咱们就从零开始,跑通第一个AI对话程序。

为什么选择Spring AI

在动手之前,先聊聊为什么要用Spring AI,而不是自己撸HTTP请求。

你可能想过用HttpClient直接调API:

// 自己拼接HTTP请求调大模型,看着就累
HttpClient client = HttpClient.newHttpClient();
String requestBody = """
    {
        "model": "qwen-plus",
        "messages": [{"role": "user", "content": "你好"}]
    }
    """;
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/chat"))
    .header("Authorization", "Bearer " + apiKey)
    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    .build();
// 还要处理响应解析、异常、重试...

这种写法有几个问题:

  • 代码量大:每次调用都要组装请求体、设置Header、解析响应
  • 没有复用性:换个模型厂商,代码几乎要重写
  • 功能受限:流式输出、对话记忆、工具调用这些高级功能实现起来很痛苦

Spring AI就是来解决这些问题的。它把各种大模型的API差异屏蔽掉了,你只需要面向统一的接口编程,底层对接的是OpenAI还是DeepSeek,代码基本不用改。

环境准备

在开始写代码之前,确保你的开发环境满足以下条件:

  • JDK 17及以上版本(Spring AI要求的最低版本)
  • Maven或Gradle构建工具
  • 一个大模型的API Key

获取API Key的方式https://articles.zsxq.com/id_7n5i83v5ae8w.html

示例中项目地址

  • 项目模块:ai-example-spring-ai

搭建项目骨架

引入核心依赖

super-agent 示例工程里,BOM版本统一定义在父工程 super-agent/pom.xml 中,当前实际使用的是:

  • Spring Boot 3.5.6
  • Spring AI 1.1.0

ai-example-spring-ai 模块本身只保留和本文示例直接相关的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring AI OpenAI(硅基流动兼容OpenAI协议) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
</dependencies>

这里有个细节要注意:Spring AI支持多种大模型,包括OpenAI、阿里云百炼、Ollama本地模型等。你只需要换一个starter依赖,其他代码基本不用动。

Spring AI支持多种大模型,包括OpenAI、阿里云百炼、Ollama本地模型等。只需要换一个starter依赖,其他代码基本不用动。

配置API密钥

在真实项目里,application.yaml 不光配置了硅基流动的 API Key,还顺手把默认系统提示词、日志级别和端口一起收好了:

server:
  port: 7089

spring:
  application:
    name: ai-example-spring-ai
  ai:
    openai:
      base-url: https://api.siliconflow.cn
      api-key: ${SILICONFLOW_API_KEY}
      chat:
        options:
          model: Qwen/Qwen3.5-122B-A10B

app:
  ai:
    chat:
      default-system-prompt: >-
        你是一名友好的 Spring AI 入门助手,负责帮助用户快速了解 Spring AI、Java 和大模型开发基础。

logging:
  level:
    org.springframework.ai: DEBUG

出于安全考虑,建议把 API Key 放到环境变量里,而不是直接写在配置文件中。本文示例模块默认读取的是 SILICONFLOW_API_KEY

永远不要把 API Key 直接硬编码在配置文件中并提交到代码仓库!应使用环境变量(如 SILICONFLOW_API_KEY)注入,避免密钥泄露。

认识ChatClient

Spring AI提供了一个核心组件叫ChatClient,它是你和大模型打交道的主要入口。可以把它理解成一个"对话客户端",封装了请求构造、响应解析、错误处理等一系列繁琐的工作。

先来看看 ChatClient 是怎么创建的。为了让文档示例和项目保持一致,我把默认系统提示词和调试日志都合并到了同一个 Bean 里:

@Configuration
public class SpringAiChatConfig {

    @Bean
    public ChatClient chatClient(ChatModel chatModel,
                                 @Value("${app.ai.chat.default-system-prompt}") String defaultSystemPrompt) {
        return ChatClient.builder(chatModel)
                .defaultSystem(defaultSystemPrompt)
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }
}

ChatModel 是 Spring AI 自动注入的,它代表具体的大模型实现。

如果你只想先跑最小示例,把 .defaultSystem().defaultAdvisors() 去掉也一样能工作。

下面这张图展示了ChatClient在整个调用链路中的位置:

7-Blog/AI技术/SpringAI详细解析/assets/FpIMckkQgn04UdnPzkxD9JC6S0r_-2ee5fb87

实现第一次对话

有了 ChatClient,写一个对话接口就非常简单了:

@RestController
@RequestMapping("/chat")
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/ask")
    public String ask(@RequestParam(value = "question", defaultValue = "请介绍一下 Spring AI 的核心能力") String question) {
        return this.chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

这段代码做了什么?

  • prompt() - 开始构建一次对话请求
  • user(question) - 设置用户输入的问题
  • call() - 执行调用,等待模型返回
  • content() - 提取返回内容中的文本部分

启动应用后,访问 http://localhost:7089/chat/ask?question=请介绍一下SpringAI的核心能力 就能看到 AI 的回答了。

7-Blog/AI技术/SpringAI详细解析/assets/FtD2ZGrjnAb9dP-g_qSVOllZfV5h-b4f85294

流式输出:打字机效果

上面的方式有个问题:用户要等AI把整段话都生成完才能看到结果。如果回答比较长,用户就得干等着,体验不好。

更好的做法是流式输出,就像ChatGPT那样一个字一个字往外蹦。Spring AI对这个支持得很好:

@GetMapping(value = "/stream", produces = "text/html;charset=utf-8")
public Flux<String> stream(
        @RequestParam(value = "question", defaultValue = "请用三点介绍 Spring AI 为什么适合 Java 项目") String question) {
    return this.chatClient.prompt()
            .user(question)
            .stream()
            .content();
}

call()换成stream(),返回类型从String变成Flux<String>,就完成了流式输出的改造。

这里用到了Reactor框架的Flux类型,它代表一个异步的数据流。每当大模型生成一小段文本,就会通过这个流推送给前端,实现实时显示的效果。

注意事项:接口的produces要设置成text/html;charset=utf-8

启动应用后,访问 http://localhost:7089/chat/stream?question=请介绍一下SpringAI的核心能力 就能看到 AI 的流式回答了。

7-Blog/AI技术/SpringAI详细解析/assets/FvRQ_ZeWaCW7ElAh5nG96mAHJ9EU-42912a94

给AI设定角色

你可能发现了,直接问AI问题,它的回答风格是通用的。但在实际业务中,我们往往希望AI扮演某个特定角色。

在这个示例模块里,我把默认系统提示词放到了配置文件中,让 /chat/ask 默认扮演一个“Spring AI 入门助手”。如果你想在单次请求里覆盖默认角色,可以像下面这样做:

@GetMapping("/interview")
public String interview(
        @RequestParam(value = "question", defaultValue = "请介绍一下 HashMap 的实现原理") String question) {
    return this.chatClient.prompt()
            .system("你现在是一名严肃但专业的 Java 技术面试官。")
            .user(question)
            .call()
            .content();
}

这样就只会影响这一轮调用,不会改掉默认的系统提示词。

如果你想更灵活一点,项目里还额外提供了一个 /chat/ask-with-system 接口,可以直接通过参数传入 system

@GetMapping("/ask-with-system")
public String askWithSystem(
        @RequestParam(value = "system", required = false) String system,
        @RequestParam(value = "question", defaultValue = "请介绍一下 Spring AI") String question) {
    ChatClient.ChatClientRequestSpec requestSpec = this.chatClient.prompt();
    if (StringUtils.hasText(system)) {
        requestSpec = requestSpec.system(system);
    }
    return requestSpec.user(question)
            .call()
            .content();
}

开启调试日志

开发阶段,你肯定想知道 Spring AI 到底给大模型发了什么请求、收到了什么响应。这时候就可以加一个日志 Advisor。这个配置在本文示例模块里已经直接打开了:

@Bean
public ChatClient chatClient(ChatModel chatModel,
                             @Value("${app.ai.chat.default-system-prompt}") String defaultSystemPrompt) {
    return ChatClient.builder(chatModel)
            .defaultSystem(defaultSystemPrompt)
            .defaultAdvisors(new SimpleLoggerAdvisor())
            .build();
}

SimpleLoggerAdvisor 是 Spring AI 内置的日志记录器,它会打印每次请求和响应的详细内容。不过要注意,它的日志级别是 DEBUG,所以你至少要把 org.springframework.ai 的日志级别调低:

SimpleLoggerAdvisor 的日志级别是 DEBUG,需要在配置文件中将 org.springframework.ai 的日志级别设置为 DEBUG 才能看到输出。如果用了自定义 log4j2.xml,还要确保 Console Appender 允许 DEBUG 输出。

logging:
  level:
    org.springframework.ai: DEBUG

如果你项目里和本文示例一样用了自定义 log4j2.xml,还要确保 Console Appender 允许 DEBUG 输出。ai-example-spring-ai 模块已经在 src/main/resources/log4j2.xml 里同步处理好了。

这样在控制台就能看到类似这样的输出:

DEBUG SimpleLoggerAdvisor - request: ChatClientRequest[prompt=Prompt{messages=[UserMessage{content='你好'...}]}]
DEBUG SimpleLoggerAdvisor - response: {"result":{"output":{"text":"你好!有什么我可以帮助你的吗?"...}}}

Advisor是什么

刚才用到了Advisor这个概念,这里简单解释一下。

Advisor可以理解成对话请求的"拦截器"或"增强器",和Spring AOP里的Advisor设计思想类似。它的工作原理是这样的:

7-Blog/AI技术/SpringAI详细解析/assets/Fteqt2-TlPgPaZW69VAhAL6Zd9rU-8ecc8a92

Advisor 是 Spring AI 的扩展机制,类似于 Spring AOP 中的切面。你可以通过 Advisor 实现日志记录、对话记忆、内容审核、性能监控等能力,而不需要修改业务代码。

Spring AI内置了不少实用的Advisor,后面的章节会详细介绍。

小结

通过这篇文章,你已经学会了:

  • Spring AI的定位和价值
  • 如何引入依赖和配置API Key
  • ChatClient的基本使用方法
  • 同步调用和流式调用的区别
  • 如何设置系统提示词定义AI角色
  • 使用Advisor记录调试日志

这只是Spring AI的冰山一角。接下来的文章会深入讲解更多高级特性,比如结构化输出、对话记忆、工具调用等。